package cn.easyplatform.studio.dao.transaction;

import cn.easyplatform.lang.Lang;
import cn.easyplatform.studio.dao.DaoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 用模板的方式操作事务
 * 
 * @author <a href="mailto:shiny_vc@163.com">陈云亮</a>
 */
public abstract class JdbcTransactions {

	private static final Logger log = LoggerFactory.getLogger(JdbcTransactions.class);

	private static final ThreadLocal<List<ConnectionInfo>> resources = new ThreadLocal<List<ConnectionInfo>>();

	private final static ThreadLocal<Transaction> trans = new ThreadLocal<Transaction>();

	private final static ThreadLocal<Integer> count = new ThreadLocal<Integer>();

	/**
	 * @param level
	 * @throws Exception
	 */
	private static void _begain(int level) throws SQLException {
		Transaction tn = trans.get();
		if (null == tn) {
			tn = new JdbcTransaction();
			tn.setLevel(level);
			trans.set(tn);
			count.set(0);
			log.debug("Start New Transaction id=%d, level=%d", tn.getId(),
					level);
		} else {
			log.debug("Attach Transaction    id=%d, level=%d", tn.getId(),
					level);
		}
		int tCount = count.get() + 1;
		count.set(tCount);

	}

	/**
	 * @throws SQLException
	 */
	private static void _commit() throws SQLException {
		count.set(count.get() - 1);
		Transaction tn = trans.get();
		if (count.get() == 0) {
			log.debug("Transaction Commit id=" + tn.getId());
			tn.commit();
		} else {
			log.debug("Transaction delay Commit id=%d, count=%d", tn.getId(),
					count.get());
		}
	}

	public static final Transaction get() {
		return trans.get();
	}

	/**
	 * 
	 */
	private static void _depose() {
		if (count.get() != null && count.get() == 0 && trans.get() != null) {
			try {
				log.debug("Transaction depose id=%d, count=%s", trans.get()
						.getId(), count.get());
				trans.get().close();
			} finally {
				trans.set(null);
				count.set(null);
			}
		}
	}

	/**
	 * @param num
	 */
	private static void _rollback(Integer num) {
		count.set(num);
		if (trans.get() != null) {
			if (count.get() == 0) {
				log.debug("Transaction rollback id=%s, count=%s", trans.get()
						.getId(), num);
				trans.get().rollback();
			} else {
				log.debug("Transaction delay rollback id=%s, count=%s", trans
						.get().getId(), num);
			}
		}
	}

	/**
	 * 执行一组原子操作，默认的事务级别为: TRANSACTION_READ_COMMITTED。详细请看 exec(int level,
	 * Atom... atoms) 函数的说明
	 * 
	 * @param atoms
	 *            原子操作对象
	 */
	public static void exec(Atom... atoms) {
		exec(Connection.TRANSACTION_READ_COMMITTED, atoms);
	}

	/**
	 * 执行一组原子操作，并指定事务级别。
	 * <p>
	 * 这里需要注意的是，Easyplatform 支持事务模板的无限层级嵌套。
	 * 这里，如果每一层嵌套，指定的事务级别有所不同，不同的数据库，可能引发不可预知的错误。
	 * <p>
	 * 所以，嵌套的事务模板的事务，将以最顶层的事务为级别为标准。就是说，如果最顶层的事务级别为
	 * 'TRANSACTION_READ_COMMITTED'，那么下面所包含的所有事务，无论你指定什么样的事务级别，都是
	 * 'TRANSACTION_READ_COMMITTED'， 这一点，由抽象类 Transaction 来保证。其 setLevel
	 * 当被设置了一个大于 0 的整数以后，将不再 接受任何其他的值。
	 * <p>
	 * 你可以通过继承 Transaction 来修改这个默认的行为，当然，这个行为修改一般是没有必要的。
	 * <p>
	 * 另外，你还可能需要知道，通过 Transactions.setup 方法，能让整个虚拟机的 Easyplatform 事务操作都使用你的
	 * Transaction 实现
	 * 
	 * @param level
	 *            事务的级别。
	 *            <p>
	 *            你可以设置的事务级别是：
	 *            <ul>
	 *            <li>java.sql.Connection.TRANSACTION_NONE
	 *            <li>java.sql.Connection.TRANSACTION_READ_UNCOMMITTED
	 *            <li>java.sql.Connection.TRANSACTION_READ_COMMITTED
	 *            <li>java.sql.Connection.TRANSACTION_REPEATABLE_READ
	 *            <li>java.sql.Connection.TRANSACTION_SERIALIZABLE
	 *            </ul>
	 *            不同的数据库，对于 JDBC 事务级别的规范，支持的力度不同。请参看相应数据库的文档，已
	 *            确定你设置的数据库事务级别是否被支持。
	 * @param atoms
	 *            原子操作对象
	 * @see cn.easyplatform.transaction.Transaction
	 * @see java.sql.Connection
	 */
	public static void exec(int level, Atom... atoms) {
		if (null == atoms)
			return;
		int num = count.get() == null ? 0 : count.get();
		try {
			_begain(level);
			for (Atom atom : atoms)
				atom.run();
			_commit();
		} catch (Throwable e) {
			_rollback(num);
			throw Lang.wrapThrow(e);
		} finally {
			_depose();
		}
	}

	/* ===========================下面暴露几个方法给喜欢 try...catch...finally 的人 ===== */

	/**
	 * 开始一个事务，级别为 TRANSACTION_READ_COMMITTED
	 * <p>
	 * 你需要手工用 try...catch...finally 来保证你提交和关闭这个事务
	 * 
	 * @throws Exception
	 */
	public static void begin() throws SQLException {
		_begain(Connection.TRANSACTION_READ_COMMITTED);
	}

	/**
	 * 开始一个指定事务
	 * <p>
	 * 你需要手工用 try...catch...finally 来保证你提交和关闭这个事务
	 * 
	 * @param level
	 *            指定级别
	 * 
	 * @throws Exception
	 */
	public static void begin(int level) throws SQLException {
		_begain(level);
	}

	/**
	 * 提交事务，执行它前，你必需保证你已经手工开始了一个事务
	 * 
	 * @throws Exception
	 */
	public static void commit() throws SQLException {
		_commit();
	}

	/**
	 * 回滚事务，执行它前，你必需保证你已经手工开始了一个事务
	 * 
	 * @throws Exception
	 */
	public static void rollback() {
		Integer c = count.get();
		if (c == null)
			c = Integer.valueOf(0);
		else if (c > 0)
			c--;
		_rollback(c);
	}

	/**
	 * 关闭事务，执行它前，你必需保证你已经手工开始了一个事务
	 * 
	 * @throws Exception
	 */
	public static void close() {
		_depose();
	}

	/**
	 * 如果在事务中,则返回事务的连接,否则直接从数据源取一个新的连接
	 */
	public static Connection getConnection(DataSource ds) throws SQLException {
		if (trans.get() == null) {
			if (resources.get() == null)
				resources.set(new ArrayList<ConnectionInfo>());
			for (ConnectionInfo p : resources.get())
				if (p.getDs() == ds)
					return p.getConn();
			Connection conn = ds.getConnection();
			resources.get().add(new ConnectionInfo(ds, conn));
			return conn;
		} else
			return trans.get().getConnection(ds);
	}

	/**
	 * 清除正常的连接以及未及时关闭的事务连接
	 */
	public static void clear() {
		if (trans.get() != null) {
			try {
				log.debug("Transaction depose id=%d, count=%s", trans.get()
						.getId(), count.get());
				trans.get().close();
			} finally {
				trans.remove();
				count.remove();
			}
		}
		if (resources.get() == null)
			return;
		for (ConnectionInfo p : resources.get())
			DaoUtils.closeQuietly(p.getConn());
		resources.get().clear();
		resources.remove();
	}
}
